package com.rede.didiok.search.service.impl;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.rede.didiok.common.constant.conf.SysConf;
import com.rede.didiok.common.dto.es.RankMemberEsModel;
import com.rede.didiok.common.schema.AggEsDoc;
import com.rede.didiok.common.utils.StringUtils;
import com.rede.didiok.search.config.ElasticsearchConfig;
import com.rede.didiok.search.constant.EsConstant;
import com.rede.didiok.search.dto.AggEsDocument;
import com.rede.didiok.search.dto.SearchParamDto;
import com.rede.didiok.search.feign.RankFeignService;
import com.rede.didiok.search.mapper.AggEsDocMapper;
import com.rede.didiok.search.service.AggEsDocService;


/**
 * @program: elasticsearch-tutorials
 * @doc: 批量操作API https://blog.csdn.net/weixin_44282094/article/details/125622021
 * @description: 产品文档相关服务的实现类
 * @author: 陌溪
 * @create: 2022年8月21日22:52:31
 **/
@Slf4j
@Service
public class AggEsDocServiceImpl implements AggEsDocService {


    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private RankFeignService rankFeignService;

    @Resource
    private AggEsDocMapper aggEsDocMapper;

    @Override
    public Map<String, Object> search(SearchParamDto param) {
        // 检索结果
        Map<String, Object> map = new HashMap<>();

        // 一、动态构建出查询需要的DSL语句: 即组装检索请求语句
        SearchRequest searchRequest = buildSearchRequrest(param);

        try {
            //2、执行检索请求
            SearchResponse response = restHighLevelClient.search(searchRequest, ElasticsearchConfig.COMMON_OPTIONS);

            //3、分析响应数据封装成我们需要的格式
            map = buildSearchResult(response, param);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return map;
    }

    @Override
    public Boolean bulkCreate(List<AggEsDoc> aggEsDocs) {

        //保存到es
        //1、给es中建立索引。didiok_rank，建立好映射关系。

        //2、给es中保存这些数据
        //BulkRequest bulkRequest, RequestOptions options
        BulkRequest bulkRequest = new BulkRequest();
        for (AggEsDoc item : aggEsDocs) {
            AggEsDocument aggEsDocument = new AggEsDocument();
            BeanUtils.copyProperties(item, aggEsDocument);
            // 过滤掉特殊的html字符
            if (StringUtils.isNotEmpty(aggEsDocument.getTitle())) {
                aggEsDocument.setTitle(StringUtils.filterHtmlTags(aggEsDocument.getTitle()));
            }
            if (StringUtils.isNotEmpty(aggEsDocument.getSummary())) {
                aggEsDocument.setSummary(StringUtils.filterHtmlTags(aggEsDocument.getSummary()));
            }
            //1、构造保存请求
            IndexRequest indexRequest = new IndexRequest(EsConstant.ES_INDEX);
            indexRequest.id(aggEsDocument.getUid());
            String s = JSON.toJSONString(aggEsDocument);
            indexRequest.source(s, XContentType.JSON);

            bulkRequest.add(indexRequest);
        }
        try {
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, ElasticsearchConfig.COMMON_OPTIONS);
            //TODO 1、如果批量错误
            boolean b = bulk.hasFailures();
            if(b) {
                log.error("商品上架错误");
            }
            List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
                return item.getId();
            }).collect(Collectors.toList());
            log.info("榜单成员发布完成：{}，返回数据：{}",collect,bulk.toString());
            return !b;
        } catch (Exception e) {
            log.error("[bulkCreate] 插入ES索引失败");
            e.printStackTrace();
        }


        return false;
    }

    /**
     * 批量更新文档
     *
     * @param aggEsDocs
     * @return
     * @throws Exception
     */
    @Override
    public Boolean bulkUpdate(List<AggEsDoc> aggEsDocs) {

        //给es中更新这些数据
        //BulkRequest bulkRequest, RequestOptions options
        BulkRequest bulkRequest = new BulkRequest();
        for (AggEsDoc item : aggEsDocs) {
            AggEsDocument aggEsDocument = new AggEsDocument();
            BeanUtils.copyProperties(item, aggEsDocument);
            // 过滤掉特殊的html字符
            if (StringUtils.isNotEmpty(aggEsDocument.getTitle())) {
                aggEsDocument.setTitle(StringUtils.filterHtmlTags(aggEsDocument.getTitle()));
            }
            if (StringUtils.isNotEmpty(aggEsDocument.getSummary())) {
                aggEsDocument.setSummary(StringUtils.filterHtmlTags(aggEsDocument.getSummary()));
            }
            //1、构造更新请求
            UpdateRequest updateRequest = new UpdateRequest(EsConstant.ES_INDEX, aggEsDocument.getUid());

            String s = JSON.toJSONString(aggEsDocument);
            updateRequest.doc(s, XContentType.JSON);

            bulkRequest.add(updateRequest);
        }
        try {
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, ElasticsearchConfig.COMMON_OPTIONS);

            //TODO 1、如果批量错误
            boolean b = bulk.hasFailures();
            if(b) {
                log.error("商品上架错误");
            }
            List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
                return item.getId();
            }).collect(Collectors.toList());
            log.info("榜单成员发布完成：{}，返回数据：{}",collect,bulk.toString());


            return !b;
        } catch (Exception e) {
            log.error("[bulkCreate] 更新ES索引失败");
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public Boolean bulkDelete(List<String> docIds) {

        //BulkRequest bulkRequest, RequestOptions options
        BulkRequest bulkRequest = new BulkRequest();
        for (String docId : docIds) {
            //1、构造保存请求
            IndexRequest indexRequest = new IndexRequest(EsConstant.ES_INDEX);
            indexRequest.id(docId);

            bulkRequest.add(indexRequest);
        }
        try {
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, ElasticsearchConfig.COMMON_OPTIONS);

            //TODO 1、如果批量错误
            boolean b = bulk.hasFailures();
            if(b) {
                log.error("批量删除索引错误");
            }
            List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
                return item.getId();
            }).collect(Collectors.toList());
            log.info("批量删除索引完成：{}，返回数据：{}",collect,bulk.toString());


            return !b;
        } catch (Exception e) {
            log.error("[bulkCreate] 更新ES索引失败");
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public Boolean deleteIndex(String indexName) {
        log.info("[deleteIndex] 删除索引: {}", indexName);
        return aggEsDocMapper.deleteIndex(indexName);
    }

    /**
     * 准备检索请求
     * #模糊匹配，过滤（按照属性，分类，品牌，价格区间，库存），排序，分页，高亮，聚合分析
     *
     * @return
     */
    private SearchRequest buildSearchRequrest(SearchParamDto param) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        /**
         * 1 查询、过滤（按照分类、榜单、投票区间、等级、属性）
         */
        // 1、构建 bool-query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 1.1、must-模糊匹配
        String keyword = param.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            // 1.1.1 多字段匹配，并给字段设置权重
            boolQuery.must(QueryBuilders.multiMatchQuery(keyword,"title", "summary", "content").field("title",10).field("summary",5));
        }

        // 1.2、bool-filter
        // 1.2.1、按照榜单id查询
        String rankUid = param.getRankUid();
        if (StringUtils.isNotEmpty(rankUid)) {
            boolQuery.filter(QueryBuilders.termsQuery("rankUid", rankUid));
        }

        // 1.2.2、按照等级id查询
        String gradeUid = param.getGradeUid();
        if (StringUtils.isNotEmpty(gradeUid)) {
            boolQuery.filter(QueryBuilders.termsQuery("gradeUid", gradeUid));
        }

        //把以前的所有条件都拿来进行封装
        sourceBuilder.query(boolQuery);

        /**
         * 2 排序，分页，高亮，
         */
        // 2.1、排序
        String sortStr = param.getSort();
        if (!StringUtils.isEmpty(sortStr)) {
            //sort=hotScore_asc/desc  sort=voteCount_asc/desc
            String[] s = sortStr.split("_");
            SortOrder order = s[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
            sourceBuilder.sort(s[0], order);
        }

        //2.1、分页
        /*
         * pageSize:5
         * currentPage:1  from:0  size:5  [0,1,2,3,4]
         * currentPage:2  from:5   size:5
         * from = (currentPage-1)*size
         */
        Integer currentPage = param.getCurrentPage();
        Integer pageSize = param.getPageSize();
        sourceBuilder.from((currentPage - 1) * pageSize);
        sourceBuilder.size(pageSize);

        //2.2、高亮
        if (!StringUtils.isEmpty(keyword)) {
            HighlightBuilder highlight = new HighlightBuilder();
            highlight.field("title").field("summary");
            highlight.preTags("<b style='color:red'>");
            highlight.postTags("</b>");
            sourceBuilder.highlighter(highlight);
        }

        System.out.println("构建的DSL" + sourceBuilder.toString());

        // 3 构造检索请求
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.ES_INDEX}, sourceBuilder);

        return searchRequest;
    }

    /**
     * 构建结果数据
     *
     * @param response
     * @return
     */
    private Map<String, Object> buildSearchResult(SearchResponse response, SearchParamDto param) {
        Map<String, Object> map = new HashMap<>();

        /**
         * 从查询结果的source中获取
         */
        //1、返回的所有查询到的榜单成员
        SearchHits searchHits = response.getHits();
        List<RankMemberEsModel> esModels = new ArrayList<>();
        SearchHit[] hits = searchHits.getHits();
        if (hits == null || hits.length <= 0) {
            return new HashMap<>();
        }

        List<AggEsDoc> aggEsDocList = new ArrayList<>();
        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString();
            AggEsDoc aggEsDoc = JSON.parseObject(sourceAsString, AggEsDoc.class);

            // 将高亮显示部分存储到esModel中
            if (!StringUtils.isEmpty(param.getKeyword())) {
                HighlightField title = hit.getHighlightFields().get("title");
                String string1 = title.getFragments()[0].string();
                aggEsDoc.setTitle(string1);

                HighlightField summary = hit.getHighlightFields().get("summary");
                String string2 = summary.getFragments()[0].string();
                aggEsDoc.setSummary(string2);
            }
            // 过滤掉内容
            aggEsDoc.setContent("");
            aggEsDocList.add(aggEsDoc);
        }
        map.put(SysConf.RANK_MEMBER_LIST, aggEsDocList);

        /**
         * 2分页信息封装
         */
        //2.1、分页信息:页码
        map.put(SysConf.CURRENT_PAGE, param.getCurrentPage());
        int pageSize = param.getPageSize();
        map.put(SysConf.PAGE_SIZE, pageSize);
        //2.2、分页信息:总记录数
        long total = searchHits.getTotalHits().value;
        map.put(SysConf.TOTAL, total);
        //2.3、分页信息:总页码-计算  11/2 = 5 .. 1
        int totalPages = (int) total % pageSize == 0 ? (int) total / pageSize : ((int) total / pageSize + 1);
        map.put(SysConf.TOTAL_PAGE, totalPages);

        return map;

    }

}
